using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;

namespace Windy.Transformations
{
    public class Transformation<TKey, TInput, TOutput>
    {
        public Transformation(Func<TInput, TKey> keyExtractor, params ITransformRule<TKey>[] rules)
        {
            Contract.Requires(keyExtractor != null, "keyExtractor is null.");
            Contract.Requires(rules != null, "rules is null.");

            Initialize(keyExtractor, rules.ToArray());
        }

        public Transformation(Func<TInput, TKey> keyExtractor, IEnumerable<ITransformRule<TKey>> rules)
        {
            Contract.Requires(keyExtractor != null, "keyExtractor is null.");
            Contract.Requires(rules != null, "rules is null.");

            Initialize(keyExtractor, rules.ToArray());
        }

        protected void Initialize(Func<TInput, TKey> keyExtractor, IList<ITransformRule<TKey>> rules)
        {
            Contract.Requires(keyExtractor != null, "keyExtractor is null.");
            Contract.Requires(rules != null, "rules is null.");

            this.KeyExtractor = keyExtractor;

            //Comparison<ILRule<TKey>> comparison = (a, b) => (int)a.OutputType - (int)b.OutputType;
            //Array.Sort(rules, comparison); // It improves the performance to sort the rules by the number of items of the rule output

            Rules = new ReadOnlyCollection<ITransformRule<TKey>>(rules);
            foreach (var fo in rules)
            {
                if (!fo.CheckRuleDataType<TKey, TInput, TOutput>())
                {
                    throw new ArgumentException("Invalid Rule was found in rules", "rules");
                }
            }
        }

        protected Func<TInput, TKey> KeyExtractor;
        public virtual TKey ExtractKey(TInput item)
        {
            return KeyExtractor(item);
        }

        public ReadOnlyCollection<ITransformRule<TKey>> Rules { get; private set; }

        public TransformRuleTypes ApplyToItem(TInput input, out dynamic output)
        {
            TKey key = ExtractKey(input);

            TransformRuleTypes result = TransformRuleTypes.VoidTransformRule;
            output = null;

            foreach (var foRule in Rules)
            {
                if (foRule.CheckCondition(key))
                {
                    switch (foRule.RuleType)
                    {
                        case TransformRuleTypes.VoidTransformRule:
                            output = null;
                            result = TransformRuleTypes.VoidTransformRule;
                            break;
                        case TransformRuleTypes.MonoTransformRule:
                            output = foRule.TransformAsMono<TKey, TInput, TOutput>(key, input);
                            result = TransformRuleTypes.MonoTransformRule;
                            break;
                        case TransformRuleTypes.VaryTransformRule:
                            output = foRule.TransformAsVary<TKey, TInput, TOutput>(key, input);
                            result = TransformRuleTypes.VaryTransformRule;
                            break;
                        default:
                            Contract.Assert(false, "Unexpected Enumeration Value");
                            break;
                    }
                    break;
                }
            }

            return result;
        }

        public IEnumerable<TOutput> ApplyTo(IEnumerable<TInput> inputs)
        {
            Contract.Requires<ArgumentNullException>(inputs != null, "items is null.");

            foreach (var foInput in inputs)
            {
                dynamic output;
                switch (ApplyToItem(foInput, out output))
                {
                    case TransformRuleTypes.VoidTransformRule:
                        // Do Nothing
                        break;
                    case TransformRuleTypes.MonoTransformRule:
                        yield return output;
                        break;
                    case TransformRuleTypes.VaryTransformRule:
                        foreach (TOutput foOutput in output)
                        {
                            yield return foOutput;
                        }
                        break;
                    default:
                        Contract.Assert(false, "Unexpected Enumeration Value");
                        break;
                }
            }
        }

        public void ApplyTo(IEnumerable<TInput> inputs, ITransformSink<TInput, TOutput> sink)
        {
            Contract.Requires(inputs != null, "items is null.");
            Contract.Requires(sink != null, "handler is null.");

            sink.OnTransformBegun();

            try
            {
                foreach (var foInput in inputs.AsIndexedEnumerable())
                {
                    dynamic output;
                    switch (ApplyToItem(foInput.Item, out output))
                    {
                        case TransformRuleTypes.VoidTransformRule:
                            sink.OnVoidTransform(foInput.Index, foInput.Item);
                            break;
                        case TransformRuleTypes.MonoTransformRule:
                            sink.OnMonoTransform(foInput.Index, foInput.Item, output);
                            break;
                        case TransformRuleTypes.VaryTransformRule:
                            sink.OnVaryTransform(foInput.Index, foInput.Item, output);
                            break;
                        default:
                            Contract.Assert(false, "Unexpected Enumeration Value");
                            break;
                    }
                }
            }
            catch (Exception)
            {
                sink.OnTransformAborted();
                return;
            }

            sink.OnTransformCompleted();
        }
    }
}
